/*
 * Copyright 2020 zytech
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "erpc_dual_serial_transport.h"
#include "erpc_message_buffer.h"
#include "erpc_serial.h"
#include <cstdio>
#include <string>
#include <termios.h>
#include <cassert>

using namespace erpc;

// Set this to 1 to enable debug logging.
// TODO fix issue with the transport not working on Linux if debug logging is disabled.
//#define SERIAL_TRANSPORT_DEBUG_LOG (1)

#if SERIAL_TRANSPORT_DEBUG_LOG
#define SERIAL_DEBUG_PRINT(_fmt_, ...) printf(_fmt_, ##__VA_ARGS__)
#define SERIAL_DEBUG_ERR(_msg_) err(errno, _msg_)
#else
#define SERIAL_DEBUG_PRINT(_fmt_, ...)
#define SERIAL_DEBUG_ERR(_msg_)
#endif

/** data segment type flag　from service send */
const static char* SERVER_C = "SVR";
/** data segment type flag　from client send */
const static char* CLIENT_C = "CLT";

#define U32_SERVER_C (*((uint32_t*)SERVER_C))
#define U32_CLIENT_C (*((uint32_t*)CLIENT_C))

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

DualSerialTransport::DualSerialTransport(const char *portName, speed_t baudRate, bool isServer)
: m_serialHandle(0)
, m_portName(portName)
, m_baudRate(baudRate)
, m_isServer(isServer)
, m_serverThread(serverThreadStub)
, m_runServer(false)
, m_selfSem(NULL)
, m_extSem(NULL)
, m_switchSem(NULL)
, m_sendLock()
, m_segType(isServer ? U32_SERVER_C : U32_CLIENT_C)
{
}

DualSerialTransport::~DualSerialTransport(void)
{
    serial_close(m_serialHandle);
}

erpc_status_t DualSerialTransport::configure(Semaphore *selfSem,Semaphore *switchSem,Mutex *sendLock,Semaphore *extSem)
{   
    assert(NULL != selfSem);
    this->m_selfSem = selfSem;
    assert(NULL != switchSem);
    this->m_switchSem = switchSem;
    assert(NULL != sendLock);
	this->m_sendLock = sendLock;
    if(this->m_isServer)
    {
        assert(NULL != extSem);
        this->m_extSem = extSem;
    }

    return kErpcStatus_Success;
}
erpc_status_t DualSerialTransport::init(uint8_t vtime, uint8_t vmin, int *serialHandle)
{
    if(m_isServer)
    {
        m_serialHandle = serial_open(m_portName);
        if (-1 == m_serialHandle)
        {
            return kErpcStatus_InitFailed;
        }
        if (!isatty(m_serialHandle))
        {
            return kErpcStatus_InitFailed;
        }
        if (-1 == serial_setup(m_serialHandle, m_baudRate))
        {
            return kErpcStatus_InitFailed;
        }
        if (-1 == serial_set_read_timeout(m_serialHandle, vtime, vmin))
        {
            return kErpcStatus_InitFailed;
        }
        if (-1 == tcflush(m_serialHandle, TCIOFLUSH))
        {
            return kErpcStatus_InitFailed;
        }
        if(serialHandle)
        {
            *serialHandle = m_serialHandle;
        }
    }
    else
    {
        // use input serial handle for client transport
    	assert(serialHandle && *serialHandle > 0);
        m_serialHandle = *serialHandle;
    }
    return kErpcStatus_Success;
}


erpc_status_t DualSerialTransport::open(void)
{
    if (m_isServer)
    {
        m_runServer = true;
        m_serverThread.start(this);
    }
    return kErpcStatus_Success;
}
erpc_status_t DualSerialTransport::close(void)
{
    if (m_isServer)
    {
        m_runServer = false;
        if(m_serialHandle)
        {
        	SERIAL_DEBUG_PRINT("Close serial handle %d",m_serialHandle);
       		serial_close(m_serialHandle);
        }
    }
   	m_serialHandle = 0;
    return kErpcStatus_Success;
}

erpc_status_t DualSerialTransport::underlyingSend(const uint8_t *data, uint32_t size)
{
    SERIAL_DEBUG_PRINT("%s: block for send\n",(char*)(&m_segType)); 
    Mutex::Guard guard(*m_sendLock);
    if(sizeof(m_segType) != serial_write(m_serialHandle, (char*)&m_segType, sizeof(m_segType)))
    {
        SERIAL_DEBUG_PRINT("%s: fail to send first char",(char*)(&m_segType));
        return kErpcStatus_SendFailed;
    }
    SERIAL_DEBUG_PRINT("%s: SEND %d bytes\n",(char*)(&m_segType),size);
    uint32_t bytesWritten = serial_write(m_serialHandle, (char *)data, size);
    SERIAL_DEBUG_PRINT("%s: SEND %d bytes,sent %d bytes \n",(char*)(&m_segType),size,bytesWritten);
    return size != bytesWritten ? kErpcStatus_SendFailed : kErpcStatus_Success;
}
erpc_status_t DualSerialTransport::underlyingReceive(uint8_t *data, uint32_t size)
{
    SERIAL_DEBUG_PRINT("%s: block for receive\n",(char*)(&m_segType)); 
    m_selfSem->get();
    SERIAL_DEBUG_PRINT("%s: RECEIVE required %d bytes\n",(char*)(&m_segType),size);
    uint32_t bytesRead = serial_read(m_serialHandle, (char *)data, size);
    SERIAL_DEBUG_PRINT("%s: RECEIVE required %d bytes read %d\n",(char*)(&m_segType),size,bytesRead);
    SERIAL_DEBUG_PRINT("%s: notify the switcher thread to read\n",(char*)(&m_segType)); 
    m_switchSem->put();
    return size != bytesRead ? kErpcStatus_ReceiveFailed : kErpcStatus_Success;
}
void DualSerialTransport::serverThread(void)
{
    SERIAL_DEBUG_PRINT("SWITCHER thread starting...\n");
    uint32_t stype;
    while(m_runServer)
    {
        SERIAL_DEBUG_PRINT("SWITCHER: blocking...\n");
        m_switchSem->get();
        SERIAL_DEBUG_PRINT("SWITCHER: listening...\n");
        while(m_runServer)
        {
            while((sizeof(stype) != serial_read(m_serialHandle, (char*)&stype, sizeof(stype))) && m_runServer)
            {
                // Sleep 10 ms.
                Thread::sleep(10000);
            }
            if(!m_runServer)
            {
                break;
            }
            SERIAL_DEBUG_PRINT("SWITCHER: segType=%c%c%c%c\n",((char*)&stype)[0],((char*)&stype)[1],((char*)&stype)[2],((char*)&stype)[3]);
            if(U32_CLIENT_C == stype)
            {
                // while received first char indicate the follow data is request from client 
                // notify the current(server) transport to receive
                // then block for next data segment
                SERIAL_DEBUG_PRINT("SWITCHER: notify the current(server) transport to receive\n"); 
                m_selfSem->put();
                break;
            }
            else if(U32_SERVER_C == stype)
            {
                assert(NULL != m_extSem);
                // while received first char indicate the follow data is response from server
                // notify extensive client transport to receive
                // then block for next data segment
                SERIAL_DEBUG_PRINT("SWITCHER: notify extensive client transport to receive\n"); 
                m_extSem->put();
                break;
            }
            else
            {
                // continue read next char 
                SERIAL_DEBUG_PRINT("SWITCHER:INVALID SEGMENT TYPE %c%c%c%c\n",((char*)&stype)[0],((char*)&stype)[1],((char*)&stype)[2],((char*)&stype)[3]);
            }
        }
    }
}

void DualSerialTransport::serverThreadStub(void *arg)
{
    DualSerialTransport *This = reinterpret_cast<DualSerialTransport *>(arg);
    SERIAL_DEBUG_PRINT("in serverThreadStub (arg=%p)\n", arg);
    if (This)
    {
        This->serverThread();
    }
}

